﻿// Shader-Based-Image-Processing (SBIP)
// http://code.google.com/p/sbip/
//
// Copyright © Frank Nagl, 2009-2010
// admin@franknagl.de
//
// Parts of source code of this class from project 3dsurreal
// http://code.google.com/p/3dsurreal/
//
namespace SBIP
{
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// Simple Render-To-Texturer.
    /// </summary>
    public class RTT
    {
        private GraphicsDevice graphics;
        private DepthStencilBuffer dsb;
        private DepthStencilBuffer backup;
        private VertexPositionTexture[] quadVex;
        private int[] quadIdx;
        private VertexDeclaration quadVDec;
        private Vector2 Delta;
        private RenderTarget2D Target1;

        /// <summary>
        /// Create a new instance
        /// </summary>
        /// <param name="graphics"></param>
        public RTT(GraphicsDevice graphics)
        {
            this.graphics = graphics;

            InitQuad();
        }

        private void InitQuad()
        {
            //init quad
            quadVex = new VertexPositionTexture[4];
            quadVex[0] = new VertexPositionTexture(-Vector3.UnitX - Vector3.UnitY, Vector2.UnitY);
            quadVex[1] = new VertexPositionTexture(Vector3.UnitX - Vector3.UnitY, Vector2.One);
            quadVex[2] = new VertexPositionTexture(Vector3.UnitX + Vector3.UnitY, Vector2.UnitX);
            quadVex[3] = new VertexPositionTexture(-Vector3.UnitX + Vector3.UnitY, Vector2.Zero);

            quadIdx = new int[4] { 0, 3, 1, 2 };

            quadVDec = new VertexDeclaration(graphics, VertexPositionTexture.VertexElements);
        }


        /// <summary>
        /// Set specified Rendertarget as RenderTarget 0. Also Set Depthbuffer
        /// </summary>
        private void SetTarget()
        {
            graphics.SetRenderTarget(0, Target1);
            backup = graphics.DepthStencilBuffer;
            graphics.DepthStencilBuffer = dsb;
            graphics.Clear(Color.Black);
        }

        /// <summary>
        /// Unset Rendertarget and return the Texture
        /// </summary>
        /// <returns>The result texture.</returns>
        private Texture2D UnsetTarget()
        {
            RenderTarget2D target = (RenderTarget2D)graphics.GetRenderTarget(0);
            graphics.SetRenderTarget(0, null);
            graphics.DepthStencilBuffer = backup;
            return target.GetTexture();
        }

        /// <summary>
        /// Draw a textured screen aligned quad, shader _must_ have parametrers "imageTexture" and "Delta"
        /// </summary>
        /// <param name="fx">Technique</param>
        /// <param name="Technique">Name of current technique to be selected</param>
        /// <param name="tex">The texture that is shown on the quad</param>
        private void DrawTexturedScreenQuad(Effect fx, string Technique, Texture2D tex)
        {
            graphics.VertexDeclaration = quadVDec;

            fx.CurrentTechnique = fx.Techniques[Technique];
            fx.Parameters["ImageTexture"].SetValue(tex);
            fx.Parameters["Delta"].SetValue(Delta);
            fx.CommitChanges();
            fx.Begin();
            foreach (EffectPass pass in fx.CurrentTechnique.Passes)
            {
                pass.Begin();
                graphics.DrawUserIndexedPrimitives<VertexPositionTexture>(
                    PrimitiveType.TriangleStrip,
                    quadVex, 0, 4,
                    quadIdx, 0, 2);
                pass.End();
            }
            fx.End();
        }

        /// <summary>
        /// Prepare RTT - generate targets and buffers with supplied format and texture's dimension.
        /// Default format of RenderTargets: SurfaceFormat.Color.
        /// </summary>
        /// <param name="texture">A texture with width and height</param>
        public void PrepareRTT(Texture2D texture) 
        {
            PrepareRTT(texture, SurfaceFormat.Color);
        }        

        /// <summary>
        /// Prepare RTT - generate targets and buffers with supplied format and texture's dimension.
        /// </summary>
        /// <param name="texture">A texture with width and height</param>
        /// <param name="format">The format of the RenderTargets</param>
        public void PrepareRTT(Texture2D texture, SurfaceFormat format)
        {
            //if (texture == null)
            //    int width = graphics.PresentationParameters.BackBufferWidth, 
            //                height = graphics.PresentationParameters.BackBufferHeight;
            int width = texture.Width, height = texture.Height;

            if (Target1 != null) Target1.Dispose();
            Target1 = new RenderTarget2D(graphics, width, height, 1, format);
            dsb = new DepthStencilBuffer(graphics, width, height, DepthFormat.Depth24);
            Delta = new Vector2(1.0f / (float)width, 1.0f / (float)height);
        }

        /// <summary>
        /// Applies a filter.
        /// </summary>
        /// <param name="tex">input texture</param>
        /// <param name="effect">effect to be used</param>
        /// <param name="technique">the technique name to be used</param>
        /// <param name="prepare">If set calls PrepareRTT</param>
        /// <returns>The result texture.</returns>
        public Texture2D Filter(Texture2D tex, Effect effect, string technique, bool prepare)
        {
            if (prepare)
                PrepareRTT(tex, SurfaceFormat.Color);
            SetTarget();
            DrawTexturedScreenQuad(effect, technique, tex);
            return UnsetTarget();
        }
    }
}
